package com.rongji.dfish.tool;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.bcel.Repository;
import org.apache.bcel.classfile.ClassFormatException;
import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.ConstantMethodType;

import com.rongji.dfish.base.Utils;
import com.rongji.dfish.framework.FrameworkHelper;
import com.rongji.dfish.misc.Geohash;
import com.rongji.dfish.ui.JsonObject;

/**
 * 用于找出class中内部依赖关系的，来判断代码调用是否合理
 * @author LinLW
 *
 */
public class ClassCallFinder {
	private static List<Class<?>[]> CALLS=new ArrayList<Class<?>[]>();
	public static void main(String[] args) throws ClassFormatException, IOException, ClassNotFoundException{

//		System.out.println(findCallClasses(GridLayout.class));
		List<Class<?>> uiClzes = getClassInFolderCascade(JsonObject.class);
		List<Class<?>> frameworkClzes = getClassInFolderCascade(FrameworkHelper.class);
		List<Class<?>> baseClzes = getClassInFolderCascade(Utils.class);
		List<Class<?>> miscClzes = getClassInFolderCascade(Geohash.class);
		checkClassCall(uiClzes);
		checkClassCall(frameworkClzes);
		checkClassCall(baseClzes);
		checkClassCall(miscClzes);
		
		HashSet<LinkedHashSet<Class<?>>> loops=calcCallLoop();
		showCallLoop(loops);
	}
	
	private static void showCallLoop(HashSet<LinkedHashSet<Class<?>>> loops) {
		for(LinkedHashSet<Class<?>> loop:loops){
//			Class<?>clz=row[0];
//			Class<?>callClz=row[1];
//			String name=clz.getName();
//			String callname=callClz.getName();
//			String pack=name.substring(0, name.lastIndexOf('.'));
//			String callpack=callname.substring(0, callname.lastIndexOf('.'));
//			int riskLevel=pack.contains(callpack)?4:5;
			StringBuilder sb=new StringBuilder();
			sb.append("L5\t");
			//"L5\t"+clz.getName()+"\t-->\t"+callClz.getName()
			Class<?> first=null;
			for(Class<?>clz : loop){
				if(first==null){
					first=clz;
				}
				sb.append(clz.getName());
				sb.append("\t-->\t");
			}
			sb.append(first.getName());
			System.out.println(sb);
		}
	}

	private static HashSet<LinkedHashSet<Class<?>>> calcCallLoop() {
		//步骤1 先排除不在这个图里的孤立节点。
		int size=CALLS.size();
		while(true){
			HashSet<Class<?>> clzs=new HashSet<Class<?>>(); 
			HashSet<Class<?>> callClzs=new HashSet<Class<?>>(); 
			for(Class<?>[] row:CALLS){
				clzs.add(row[0]);
				callClzs.add(row[1]);
			}
			for(Iterator<Class<?>[]> iter=CALLS.iterator();iter.hasNext();){
				Class<?>[] row=iter.next();
				if(!callClzs.contains(row[0])||!clzs.contains(row[1])){
					iter.remove();
				}
			}
			int nowSize=CALLS.size();
			if(size==nowSize)break;
//			System.out.println("第"+(++times)+"次检查，排重从"+size+"到"+nowSize);
			size=nowSize;
		}
		HashSet<LinkedHashSet<Class<?>>> allLoops=new HashSet<LinkedHashSet<Class<?>>>();
		while(CALLS.size()>0){
			//开始递归寻找可能节点。
			Class<?>[]row =CALLS.get(0);
			Class<?> start=row[0];
			ArrayList<Class<?>> path=new ArrayList<Class<?>>();
			HashSet<Class<?>> refs=new HashSet<Class<?>>();
			findLoops(allLoops,refs,path,start,CALLS);
			//删除刚才递归过程中遍历的所有节点
			for(Iterator<Class<?>[]> iter=CALLS.iterator();iter.hasNext();){
				Class<?>[] row2=iter.next();
				if(refs.contains(row2[0])){
					iter.remove();
				}
			}
		}
		return allLoops;
	}

	private static void findLoops(HashSet<LinkedHashSet<Class<?>>> allLoops, HashSet<Class<?>> refs, ArrayList<Class<?>> path, Class<?> start,
			List<Class<?>[]> CALLS2) {
		refs.add(start);
		path.add(start);
		for(Class<?>[]row:CALLS2){
			if(row[0]!=start){
				continue;
			}
			int index=path.indexOf(row[1]);
			if(index>=0){
				LinkedHashSet<Class<?>> loop=new LinkedHashSet<Class<?>>(path.subList(index, path.size()));
				allLoops.add(loop);
			}else{
				findLoops(allLoops, refs, (ArrayList<Class<?>>)path.clone(), row[1], CALLS2);
			}
		}
		
	}

	/**
	 * 判定类的调用。
	 * 不检查 java.开头的调用是否合理
	 * 不检查 本包调用是否合理。
	 * @param uiClzes
	 * @throws ClassNotFoundException 
	 * @throws IOException 
	 * @throws ClassFormatException 
	 */
	private static void checkClassCall(List<Class<?>> clzes) throws ClassFormatException, IOException, ClassNotFoundException {
		for(Class<?> clz:clzes){
			List<Class<?>>callClzes=findCallClasses(clz);
			for(Class<?> callClz:callClzes){
				int riskLevel=riskLevel(clz,callClz);
				if(riskLevel>2){
						System.out.println("L"+riskLevel+"\t"+clz.getName()+"\t-->\t"+callClz.getName());
				}
			}
		}
		
	}


	private static int riskLevel(Class<?> clz, Class<?> callClz) {
		if(clz==callClz){
			return 0;
		}
		String callname=callClz.getName();
		if(callname.startsWith("java.")||callname.startsWith("[")){
			return 0;
		}
		String name=clz.getName();
		if(callname.indexOf('$')>0||name.indexOf('$')>0){
			String shortCallname=callname.indexOf('$')>0?callname.substring(0,callname.indexOf('$')):callname;
			String shortname=name.indexOf('$')>0?name.substring(0,name.indexOf('$')):name;
			if(shortCallname.equals(shortname)){
				return 0;
			}
		}
		CALLS.add(new Class[]{clz,callClz});
		String pack=name.substring(0, name.lastIndexOf('.'));
		String callpack=callname.substring(0, callname.lastIndexOf('.'));
		if(pack.equals(callpack)){
			return 1;//同包call
		}else {
			if(pack.contains(callpack)){
				return 2;//子包calll
			}else if(confirmSave(name,callname)){
				return 2;//不同包call
			}else{
				return 3;//未认为安全的CALL
			}
		}
	}

	private static boolean confirmSave(String name, String callname) {
		if(callname.startsWith("javax.")||callname.startsWith("org.apache.")
				||callname.startsWith("org.springframework.")||callname.startsWith("org.hibernate.")
				||callname.startsWith("org.dom4j.")||callname.startsWith("org.bouncycastle.")){
			return true;
		}else if(name.startsWith("com.rongji.dfish.ui.")&&callname.startsWith("com.rongji.dfish.ui.json.")){
			return true;
		}else if(name.startsWith("com.rongji.dfish.framework.")&&callname.startsWith("com.rongji.dfish.base.")){
			return true;
		}else if(name.startsWith("com.rongji.dfish.misc.")&&callname.startsWith("com.rongji.dfish.base.")){
			return true;
		}else if(name.startsWith("com.rongji.dfish.ui.")&&callname.startsWith("com.rongji.dfish.base.")){
			return true;
		}else if(name.startsWith("com.rongji.dfish.framework.")&&callname.startsWith("com.rongji.dfish.ui.")){
			return true;
		}else if(name.startsWith("com.rongji.dfish.ui.layout.")&&callname.startsWith("com.rongji.dfish.ui.layout.grid.")){
			return true;
		}else if(name.startsWith("com.rongji.dfish.ui.helper.")&&callname.startsWith("com.rongji.dfish.ui.layout.")){
			return true;
		}else if(name.startsWith("com.rongji.dfish.ui.helper.")&&callname.startsWith("com.rongji.dfish.ui.widget.")){
			return true;
		}else if(name.startsWith("com.rongji.dfish.ui.helper.")&&callname.startsWith("com.rongji.dfish.ui.form.")){
			return true;
		}else if(name.startsWith("com.rongji.dfish.ui.layout.")&&callname.startsWith("com.rongji.dfish.ui.widget.")){
			return true;
		}
		return false;
	}


	/**
	 * 根据类获取此类同一目录和所有下级目录中类的实例
	 * @param clz 类
	 * @return 实例列表
	 */
	private static List<Class<?>> getClassInFolderCascade(Class<?> clz) {
		List<Class<?>> result = new ArrayList<Class<?>>();
		String clzName = clz.getName();
		String packageName = clzName.substring(0, clzName.lastIndexOf('.'));

		URL path = clz.getResource(clzName.substring(clzName.lastIndexOf('.') + 1) + ".class");
		File f = new File(path.getFile());
		File folder = f.getParentFile();
		File[] files = folder.listFiles();
		fillClasses(result, packageName, files);
		return result;
	}
	private static void fillClasses(List<Class<?>> result, String packageName, File[] files) {
		if (result == null || files == null) {
			return;
		}
		for (File file : files) {
			if (file.isDirectory()) {
				fillClasses(result, packageName + "." + file.getName(), file.listFiles());
			} else {
				String subName = file.getName().substring(0, file.getName().lastIndexOf('.'));
				try {
					result.add(Class.forName(packageName + "." + subName));
				} catch (Throwable t) {}
			}
		}
	}
	
	/**
	 * 找到这个类调用到的类
	 * @param clz
	 * @return
	 * @throws IOException 
	 * @throws ClassFormatException 
	 * @throws ClassNotFoundException 
	 */
	public static  List<Class<?>> findCallClasses(Class<?> clz) throws ClassFormatException, IOException, ClassNotFoundException{
		List<Class<?>> calledClasses=new ArrayList<Class<?>> ();
		JavaClass jc=Repository.lookupClass(clz.getName());
		Constant[] cons=jc.getConstantPool().getConstantPool();
		for(int i=0;i<cons.length;i++){
			Constant con=cons[i];
			if(con==null){
				continue;
			}
			if(con instanceof ConstantClass){
				ConstantClass cc=(ConstantClass)con;
				int nameIndex=cc.getNameIndex();
				ConstantUtf8 name=(ConstantUtf8) cons[nameIndex];
				if(name !=null&&name.getBytes()!=null){
					try{
						Class<?> called=Class.forName(name.getBytes().replace('/', '.'));
						calledClasses.add(called);
					}catch(Throwable ex){
						System.out.println("L5\tmissing class "+name.getBytes().replace('/', '.')+" when "+clz.getName()+" call");
					}
				}
			}
		}
		return calledClasses;
	}
}
